﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Narwhunderful.Aphelia.Framework.Graphics
{

    public static class SpriteBatchExtentions
    {

        static Texture2D _pixelTexture = null;
        static Texture2D _circleTexture = null;

        const int _circleTextureRadius = 512;
        static readonly Vector2 _circleTextureRadiusVector = new Vector2(_circleTextureRadius, _circleTextureRadius);

        static readonly Dictionary<String, List<Vector2>> _circleCache = new Dictionary<string, List<Vector2>>();
        static readonly Dictionary<String, List<Vector2>> _arcCache = new Dictionary<string, List<Vector2>>();

        public static Texture2D GetPixelTexture(this SpriteBatch spriteBatch)
        {
            EnsurePixelTexture(spriteBatch.GraphicsDevice);
            return _pixelTexture;
        }

        public static Texture2D GetCircleTexture(this SpriteBatch spriteBatch)
        {
            EnsureCircleTexture(spriteBatch.GraphicsDevice);
            return _circleTexture;
        }

        static void EnsurePixelTexture(GraphicsDevice graphicsDevice)
        {
            if (_pixelTexture == null) _pixelTexture = CreatePixelTexture(graphicsDevice);
        }

        static void EnsureCircleTexture(GraphicsDevice graphicsDevice)
        {
            if (_circleTexture == null) _circleTexture = CreateCircleTexture(graphicsDevice, _circleTextureRadius, 0, 1, 1, Color.White, Color.White);
        }

        static Texture2D CreatePixelTexture(GraphicsDevice graphicsDevice)
        {
            Texture2D pixelTexture = new Texture2D(graphicsDevice, 1, 1, false, SurfaceFormat.Color);
            pixelTexture.SetData<Color>(new Color[] { Color.White });
            return pixelTexture;
        }

        static Texture2D CreateCircleTexture(GraphicsDevice graphicsDevice, int radius, int borderWidth, int borderInnerTransitionWidth, int borderOuterTransitionWidth, Color color, Color borderColor)
        {
            int diameter = radius * 2;
            Vector2 center = new Vector2(radius, radius);
            Texture2D circle = new Texture2D(graphicsDevice, diameter, diameter, false, SurfaceFormat.Color);
            Color[] colors = new Color[diameter * diameter];
            int y = -1;
            for (int i = 0; i < colors.Length; i++)
            {
                int x = i % diameter;

                if (x == 0)
                {
                    y += 1;
                }

                Vector2 diff = new Vector2(x, y) - center;
                float length = diff.Length(); // distance.Length();

                if (length > radius)
                {
                    colors[i] = new Color(255, 255, 255, 0);
                }
                else if (length >= radius - borderOuterTransitionWidth)
                {
                    float transitionAmount = (length - (radius - borderOuterTransitionWidth)) / borderOuterTransitionWidth;
                    transitionAmount = 255 * (1 - transitionAmount);
                    colors[i] = new Color(borderColor.R, borderColor.G, borderColor.B, (byte)transitionAmount);
                }
                else if (length > radius - (borderWidth + borderOuterTransitionWidth))
                {
                    colors[i] = borderColor;
                }
                else if (length >= radius - (borderWidth + borderOuterTransitionWidth + borderInnerTransitionWidth))
                {
                    float transitionAmount = (length -
                                              (radius -
                                               (borderWidth + borderOuterTransitionWidth + borderInnerTransitionWidth))) /
                                             (borderInnerTransitionWidth + 1);
                    colors[i] = new Color((byte)MathHelper.Lerp(color.R, borderColor.R, transitionAmount),
                                          (byte)MathHelper.Lerp(color.G, borderColor.G, transitionAmount),
                                          (byte)MathHelper.Lerp(color.B, borderColor.B, transitionAmount));
                }
                else
                {
                    colors[i] = color;
                }
            }
            circle.SetData(colors);
            return circle;
        }

        //public static List<Vector2> CreateCircle(double radius, int sides)
        //{

        //    string key = radius + ":" + sides;
        //    if (_circleCache.ContainsKey(key)) return _circleCache[key];

        //    List<Vector2> vectors = new List<Vector2>();
        //    double radiansPerSide = MathHelper.TwoPi / sides;

        //    for (double theta = 0.0; theta < MathHelper.TwoPi; theta += radiansPerSide)
        //    {
        //        vectors.Add(new Vector2((float)(radius * Math.Cos(theta)), (float)(radius * Math.Sin(theta))));
        //    }

        //    //vectors.Add(new Vector2((float)(radius * Math.Cos(0)), (float)(radius * Math.Sin(0))));

        //    _circleCache.Add(key, vectors);
        //    return vectors;

        //}

        //public static List<Vector2> CreateArc(float radius, int sides, float startingAngle, float endingAngle)
        //{

        //    String key = radius + ":" + sides + ":" + startingAngle + ":" + endingAngle;
        //    if (_arcCache.ContainsKey(key)) return _arcCache[key];

        //    List<Vector2> points = new List<Vector2>();

        //    double startRadians = MathHelper.ToRadians(startingAngle);
        //    double endRadians = MathHelper.ToRadians(endingAngle);

        //    if (startRadians >= endRadians) endRadians += MathHelper.TwoPi;

        //    double radiansPerSide = (endRadians - startRadians) / sides;

        //    double theta;
        //    for (theta = startRadians; theta < endRadians; theta += radiansPerSide)
        //    {
        //        points.Add(new Vector2((float)(radius * Math.Cos(theta)), (float)(radius * Math.Sin(theta))));
        //    }

        //    //points.Add(new Vector2((float)(radius * Math.Cos(theta)), (float)(radius * Math.Sin(theta))));

        //    _arcCache.Add(key, points);

        //    return points;
        //}

        /// <summary>
        /// Adds a line sprite to the batch of sprites to be rendered, specifying the connecting points, color, thickness, and sort depth.
        /// </summary>
        /// <param name="spriteBatch"></param>
        /// <param name="point1">The location, in screen coordinates, where the first point connects.</param>
        /// <param name="point2">The location, in screen coordinates, where the second point connects.</param>
        /// <param name="thickness">The thickness of the line.</param>
        /// <param name="color">The color to tint the line.</param>
        /// <param name="layerDepth">The sorting depth of the line, between 0 (front) and 1 (back).</param>
        public static void DrawLine(this SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, float thickness, Color color, float layerDepth)
        {
            EnsurePixelTexture(spriteBatch.GraphicsDevice);
            spriteBatch.Draw(
                _pixelTexture,
                point1,
                null,
                color,
                (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X),
                Vector2.Zero,
                new Vector2(Vector2.Distance(point1, point2), thickness),
                SpriteEffects.None,
                layerDepth);
        }

        /// <summary>
        /// Adds an outlined rectangle sprite to the batch of sprites to be rendered, specifying the position, width, height, thickness, color, rotation, scale, and sort depth.
        /// </summary>
        /// <param name="spriteBatch"></param>
        /// <param name="position">The location, in screen coordinates, where the rectangle will be drawn.</param>
        /// <param name="width">The width of the rectangle.</param>
        /// <param name="height">The height of the rectangle.</param>
        /// <param name="thickness">The thickness of the rectangle.</param>
        /// <param name="color">The color to tint the rectangle.</param>
        /// <param name="rotation">The angle, in radians, to rotate the rectangle around the origin.</param>
        /// <param name="scale">Vector containing separate scalar multiples for the x- and y-axes of the rectangle.</param>
        /// <param name="layerDepth">The sorting depth of the rectangle, between 0 (front) and 1 (back).</param>
        public static void DrawRectangle(this SpriteBatch spriteBatch, Vector2 position, int width, int height, int thickness, Color color, float rotation, Vector2 scale, float layerDepth)
        {
            EnsurePixelTexture(spriteBatch.GraphicsDevice);
            Rectangle rectangleWidth = new Rectangle(0, 0, width, thickness);
            Rectangle rectangleHeight = new Rectangle(0, 0, thickness, height);
            Vector2 rectangleOrigin = new Vector2(width * 0.5f, height * 0.5f);
            Vector2 rectanglePosition = position + rectangleOrigin;
            float bottomRightRotation = rotation + MathHelper.Pi;
            spriteBatch.Draw(_pixelTexture, rectanglePosition, rectangleWidth, color, rotation, rectangleOrigin, scale, SpriteEffects.None, layerDepth);
            spriteBatch.Draw(_pixelTexture, rectanglePosition, rectangleHeight, color, rotation, rectangleOrigin, scale, SpriteEffects.None, layerDepth);
            spriteBatch.Draw(_pixelTexture, rectanglePosition, rectangleWidth, color, bottomRightRotation, rectangleOrigin, scale, SpriteEffects.None, layerDepth);
            spriteBatch.Draw(_pixelTexture, rectanglePosition, rectangleHeight, color, bottomRightRotation, rectangleOrigin, scale, SpriteEffects.None, layerDepth);
        }

        /// <summary>
        /// Adds an filled rectangle sprite to the batch of sprites to be rendered, specifying the position, width, height, color, rotation, scale, and sort depth.
        /// </summary>
        /// <param name="spriteBatch"></param>
        /// <param name="position">The location, in screen coordinates, where the rectangle will be drawn.</param>
        /// <param name="width">The width of the rectangle.</param>
        /// <param name="height">The height of the rectangle.</param>
        /// <param name="color">The color to tint the rectangle.</param>
        /// <param name="rotation">The angle, in radians, to rotate the rectangle around the origin.</param>
        /// <param name="scale">Vector containing separate scalar multiples for the x- and y-axes of the rectangle.</param>
        /// <param name="layerDepth">The sorting depth of the rectangle, between 0 (front) and 1 (back).</param>
        public static void DrawFilledRectangle(this SpriteBatch spriteBatch, Vector2 position, int width, int height, Color color, float rotation, Vector2 scale, float layerDepth)
        {
            EnsurePixelTexture(spriteBatch.GraphicsDevice);
            Vector2 rectangleOrigin = new Vector2(width * 0.5f, height * 0.5f);
            Vector2 rectanglePosition = position + rectangleOrigin;
            spriteBatch.Draw(_pixelTexture, rectanglePosition, new Rectangle(0, 0, width, height), color, rotation, rectangleOrigin, scale, SpriteEffects.None, layerDepth);
        }

        //public static void DrawPath(this SpriteBatch spriteBatch, List<Vector2> points, Color color, float thickness)
        //{
        //    for (int i = 0; i < points.Count - 1; i++) DrawLine(spriteBatch, points[i], points[i + 1], color, thickness);
        //}

        //public static void DrawPath(this SpriteBatch spriteBatch, Vector2 position, List<Vector2> points, Color color, float thickness)
        //{
        //    for (int i = 0; i < points.Count - 1; i++) DrawLine(spriteBatch, points[i] + position, points[i + 1] + position, color, thickness);
        //}

        //public static void DrawPolygon(this SpriteBatch spriteBatch, List<Vector2> points, int thickness, Color color, float rotation, Vector2 origin, Vector2 scale, float layerDepth)
        //{
        //    EnsurePixelTexture(spriteBatch.GraphicsDevice);
        //    for (int x = 0; x < points.Count - 1; x++)
        //    {
        //        spriteBatch.Draw(_pixelTexture, points[x], new Rectangle(0, 0, (int)Vector2.Distance(points[x], points[x + 1]), thickness), color, rotation, origin, scale, SpriteEffects.None, layerDepth);
        //    }
        //    spriteBatch.Draw(_pixelTexture, points[points.Count - 1], new Rectangle(0,0, (int)Vector2.Distance(points[points.Count - 1], points[0]), thickness), color, rotation, origin, scale, SpriteEffects.None, layerDepth);
        //    //DrawPath(spriteBatch, points, color, thickness);
        //    //DrawLine(spriteBatch, points[points.Count - 1], points[0], color, thickness);
        //}

        //public static void DrawPolygon(this SpriteBatch spriteBatch, Vector2 position, List<Vector2> points, Color color, float thickness)
        //{
        //    DrawPath(spriteBatch, position, points, color, thickness);
        //    DrawLine(spriteBatch, points[points.Count - 1] + position, points[0] + position, color, thickness);
        //}

        //public static void DrawCircle(this SpriteBatch spriteBatch, Vector2 point, float radius, int sides, Color color, float thickness)
        //{
        //    DrawPolygon(spriteBatch, point, DrawingHelper.CreateCircle(radius, sides), color, thickness);
        //}
        //public static void DrawFilledCircle(this SpriteBatch spriteBatch, Vector2 point, float radius, Color color)
        //{
        //    EnsureCircleTexture(spriteBatch.GraphicsDevice);
        //    spriteBatch.Draw(_circleTexture, point, null, color, 0, _circleTextureRadiusVector, radius / _circleTextureRadius, SpriteEffects.None, 0);
        //}

        //public static void DrawArc(this SpriteBatch spriteBatch, Vector2 point, float radius, int sides, float startingAngle, float endingAngle, Color color, float thickness)
        //{
        //    DrawPath(spriteBatch, point, DrawingHelper.CreateArc(radius, sides, startingAngle, endingAngle), color, thickness);
        //}

    }

}